package ecprac.drivers.def;

import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Random;

import ecprac.ea.abstracts.AbstractEA;
import ecprac.ea.abstracts.DriversUtils;
import ecprac.ea.abstracts.AbstractRace.DefaultTracks;
import ecprac.torcs.controller.Driver;

public class DefaultEA extends AbstractEA{
	static ArrayList<Record> records;
	static int RACE_NUMBER = 0;

	private static final long serialVersionUID = 1L;
	Random r = new Random(558); 

	// The population 
	DefaultDriverGenome[] population = new DefaultDriverGenome[10];

	// The fitness values
	int[] fitness = new int[population.length];

	// The number of evaluations 
	int evals;

	public Class<? extends Driver> getDriverClass(){
		return DefaultDriver.class;
	}

	public void run() {
		// start a completely new run
		run(false);
	}

	public void run(boolean continue_from_checkpoint) {

		/*
		 * 
		 * If we are not continuing from a previously
		 * terminated run, we need to initialize the population
		 * 
		 */
		if(!continue_from_checkpoint){
			//  initialization of the genomes
			initialize();
			evaluatePopulation();

		}


		// the evolutionary loop
		while (evals < 5000) {

			// find the best genome
			int best = 1;
			for(int i=0; i<population.length; i++){
				if (fitness[i] == 1) {
					best = i;
				}
			}

			// replace the worst 50% from the population with 
			// a mutated version of the best genome
			for(int i=0; i<population.length; i++){
				if(fitness[i] > population.length*0.5 ){
					population[i] = mutate(population[best]);
				}
			}

			// Re-evaluate the population on a different track
			evaluatePopulation();

			// create a checkpoint 
			// this allows you to continue this run later
			DriversUtils.createCheckpoint(this);

			System.out.println("Evaluations: " + evals);

		}

		// clear the checkpoint
		DriversUtils.clearCheckpoint();

	}

	public void evaluatePopulation(){

		// Create a new Race object with a random track
		// from the set of default tracks
		DefaultRace race = new DefaultRace();
		race.setTrack( DefaultTracks.getTrack(r.nextInt(4)) );
		race.laps = 2;
		// Run the race, fitness = rank
		// The GUI is set to true, for speedup set withGUI to false
		RACE_NUMBER++;
		fitness = race.runRace(population, false);
		writeRecords();

		// Increment the number of evaluations
		evals+=population.length;

		// Save the best genome from the population
		for(int i=0; i<population.length; i++){
			if(fitness[i] == 1){
				DriversUtils.storeGenome(population[i]);
			}
		}	
	}

	public void initialize() {

		// Create an array of DefaultDriverGenome objects and
		// assign a random number [0 1] to each gene

		for (int i = 0; i < population.length; i++) {

			DefaultDriverGenome genome = new DefaultDriverGenome();

			// Randomly initialize the genome with values [0,1]
			genome.c0 = r.nextDouble();
			genome.c1 = r.nextDouble();

			population[i] = genome;
			fitness[i] = 0;
		}

	}

	public DefaultDriverGenome mutate(DefaultDriverGenome genome) {

		DefaultDriverGenome genome2 = new DefaultDriverGenome();

		// Add some Gaussian noise
		genome2.c0 = genome.c0 + (0.05 * r.nextGaussian());
		genome2.c1 = genome.c1 + (0.05 * r.nextGaussian());

		return genome2;

	}

	public void writeRecords() {
		try {
			FileWriter fW = new FileWriter(new File("records.csv"), true);
			for (Record r : records) {
				String str = RACE_NUMBER +"," +r.sharpness +"," +r.length +"," +r.trackPos +"," +r.angle +"\n";
				fW.write(str);
			}
			fW.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}


	public static void main(String[] args) {

		/*
		 * 
		 * Start without arguments to run the EA
		 * Start with -continue to continue a previous run
		 * Start with -show to show the best found
		 * Start with -show-race to show a race with 10 copies of the best found
		 * Start with -human to race against the best found
		 * 
		 */
		records = new ArrayList<Record>();

		DefaultEA ea = new DefaultEA();
		DriversUtils.registerMemory(ea.getDriverClass());
		if(args.length > 0 && args[0].equals("-show")){
			new DefaultRace().showBest();
		} else if(args.length > 0 && args[0].equals("-show-race")){
			new DefaultRace().showBestRace();
		} else if(args.length > 0 && args[0].equals("-human")){
			new DefaultRace().raceBest();
		} else if(args.length > 0 && args[0].equals("-continue")){
			if(DriversUtils.hasCheckpoint()){
				DriversUtils.loadCheckpoint().run(true);
			} else {
				ea.run();
			}
		} else {
			ea.run();
		}
	}

}
